#ifndef STX_ENDIANNESS_HPP
#define STX_ENDIANNESS_HPP

#include <stdint.h>

#define STX_CURRENT_BYTE_ORDER       (*(int *) "\x01\x02\x03\x04")
#define STX_LITTLE_ENDIAN_BYTE_ORDER 0x04030201
#define STX_BIG_ENDIAN_BYTE_ORDER    0x01020304
#define STX_PDP_ENDIAN_BYTE_ORDER    0x02010403

#define STX_IS_LITTLE_ENDIAN (STX_CURRENT_BYTE_ORDER == STX_LITTLE_ENDIAN_BYTE_ORDER)
#define STX_IS_BIG_ENDIAN    (STX_CURRENT_BYTE_ORDER == STX_BIG_ENDIAN_BYTE_ORDER)
#define STX_IS_PDP_ENDIAN    (STX_CURRENT_BYTE_ORDER == STX_PDP_ENDIAN_BYTE_ORDER)

namespace stx {

// Determine Little-Endian or Big-Endian

// Forward declaration

template<typename T>
struct little_endian;

template<typename T>
struct big_endian;

// Little-Endian template

#pragma pack(push, 1)

template<typename T>
struct little_endian
{
    unsigned char bytes[sizeof(T)];

    little_endian(T t = T())
    {
        operator =(t);
    }

    little_endian(const little_endian<T>& t)
    {
        for (unsigned i = 0; i < sizeof(T); i++) {
            bytes[i] = t.bytes[i];
        }
    }

    little_endian(const big_endian<T>& t)
    {
        for (unsigned i = 0; i < sizeof(T); i++) {
            bytes[i] = t.bytes[sizeof(T) - 1 - i];
        }
    }

    operator const T() const
    {
        T t = T();
        for (unsigned i = 0; i < sizeof(T); i++) {
            t |= bytes[i] << (i << 3);
        }
        return t;
    }

    const T operator=(const T t)
    {
        for (unsigned i = 0; i < sizeof(T); i++) {
            bytes[i] = t >> (i << 3);
        }
        return t;
    }

    // operators

    const T operator+=(const T t)
    {
        return (*this = *this + t);
    }

    const T operator-=(const T t)
    {
        return (*this = *this - t);
    }

    const T operator*=(const T t)
    {
        return (*this = *this * t);
    }

    const T operator/=(const T t)
    {
        return (*this = *this / t);
    }

    const T operator%=(const T t)
    {
        return (*this = *this % t);
    }

    little_endian<T> operator++(int)
    {
        for (unsigned i = 0; i < sizeof(T); i++) {
            bytes[i]++;
            if (bytes[i] != 0) {
                break;
            }
        }
        return (*this);
    }

    little_endian<T>& operator++()
    {
        for (unsigned i = 0; i < sizeof(T); i++) {
            ++bytes[i];
            if (bytes[i] != 0) {
                break;
            }
        }
        return (*this);
    }

    little_endian<T> operator--(int)
    {
        for (unsigned i = 0; i < sizeof(T); i++) {
            bytes[i]--;
            if (bytes[i] != (T)(-1)) {
                break;
            }
        }
        return (*this);
    }

    little_endian<T>& operator--()
    {
        for (unsigned i = 0; i < sizeof(T); i++) {
            --bytes[i];
            if (bytes[i] != (T)(-1)) {
                break;
            }
        }
        return (*this);
    }
};

#pragma pack(pop)

// Big-Endian template

#pragma pack(push, 1)

template<typename T>
struct big_endian
{
    unsigned char bytes[sizeof(T)];

    big_endian(T t = T())
    {
        operator =(t);
    }

    big_endian(const big_endian<T>& t)
    {
        for (unsigned i = 0; i < sizeof(T); i++) {
            bytes[i] = t.bytes[i];
        }
    }

    big_endian(const little_endian<T>& t)
    {
        for (unsigned i = 0; i < sizeof(T); i++) {
            bytes[i] = t.bytes[sizeof(T) - 1 - i];
        }
    }

    operator const T() const
    {
        T t = T();
        for (unsigned i = 0; i < sizeof(T); i++) {
            t |= bytes[sizeof(T) - 1 - i] << (i << 3);
        }
        return t;
    }

    const T operator=(const T t)
    {
        for (unsigned i = 0; i < sizeof(T); i++) {
            bytes[sizeof(T) - 1 - i] = t >> (i << 3);
        }
        return t;
    }

    // operators

    const T operator+=(const T t)
    {
        return (*this = *this + t);
    }

    const T operator-=(const T t)
    {
        return (*this = *this - t);
    }

    const T operator*=(const T t)
    {
        return (*this = *this * t);
    }

    const T operator/=(const T t)
    {
        return (*this = *this / t);
    }

    const T operator%=(const T t)
    {
        return (*this = *this % t);
    }

    little_endian<T> operator++(int)
    {
        for (unsigned i = 0; i < sizeof(T); i++) {
            bytes[sizeof(T) - 1 - i]++;
            if (bytes[sizeof(T) - 1 - i] != 0) {
                break;
            }
        }
        return (*this);
    }

    little_endian<T>& operator++()
    {
        for (unsigned i = 0; i < sizeof(T); i++) {
            ++bytes[sizeof(T) - 1 - i];
            if (bytes[sizeof(T) - 1 - i] != 0) {
                break;
            }
        }
        return (*this);
    }

    little_endian<T> operator--(int)
    {
        for (unsigned i = 0; i < sizeof(T); i++) {
            bytes[sizeof(T) - 1 - i]--;
            if (bytes[sizeof(T) - 1 - i] != (T)(-1)) {
                break;
            }
        }
        return (*this);
    }

    little_endian<T>& operator--()
    {
        for (unsigned i = 0; i < sizeof(T); i++) {
            --bytes[sizeof(T) - 1 - i];
            if (bytes[sizeof(T) - 1 - i] != (T)(-1)) {
                break;
            }
        }
        return (*this);
    }
};

#pragma pack(pop)

typedef int8_t    i8;
typedef int16_t  i16;
typedef int32_t  i32;
typedef int64_t  i64;
typedef uint8_t   u8;
typedef uint16_t u16;
typedef uint32_t u32;
typedef uint64_t u64;

#ifdef BIG_ENDIAN

// Little Endian

typedef little_endian<i16> i16le;
typedef little_endian<i32> i32le;
typedef little_endian<i64> i64le;

typedef little_endian<u16> u16le;
typedef little_endian<u32> u32le;
typedef little_endian<u64> u64le;

// Big Endian

typedef i16 i16be;
typedef i32 i32be;
typedef i64 i64be;

typedef u16 u16be;
typedef u32 u32be;
typedef u64 u64be;

#else

// Little Endian

typedef i16 i16le;
typedef i32 i32le;
typedef i64 i64le;

typedef u16 u16le;
typedef u32 u32le;
typedef u64 u64le;

// Big Endian

typedef big_endian<i16> i16be;
typedef big_endian<i32> i32be;
typedef big_endian<i64> i64be;

typedef big_endian<u16> u16be;
typedef big_endian<u32> u32be;
typedef big_endian<u64> u64be;

#endif

} // namespace stx

#endif // STX_ENDIANNESS_HPP
